package cn.com.qtgl.sso.oidc.comp.interceptor;

import cn.com.qtgl.sso.oidc.comp.anno.Authentication;
import cn.com.qtgl.sso.oidc.comp.anno.Authentications;
import cn.com.qtgl.sso.oidc.comp.security.HttpAuthorizationIdentificationMessenger;
import cn.com.qtgl.sso.oidc.comp.service.TokenService;
import cn.com.qtgl.sso.oidc.comp.tools.AuthenticationToolUtils;
import cn.com.qtgl.sso.oidc.model.enums.AuthenticationAction;
import cn.hutool.core.io.IoUtil;
import cn.melonlib.cores.commons.comp.web.ctrl.BasicController;
import cn.melonlib.cores.graphql.comp.web.anno.GraphqlApi;
import cn.melonlib.cores.graphql.comp.web.bean.GraphqlArguments;
import cn.melonlib.cores.graphql.comp.web.utils.GraphqlArgumentsUtils;
import cn.melonlib.objects.maps.JsonMap;
import lombok.Data;
import org.hibernate.service.spi.ServiceException;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class IdentificationInterceptor extends ApplicationObjectSupport implements HandlerInterceptor {

    @Resource
    @Lazy
    private HttpAuthorizationIdentificationMessenger identificationMessenger;

    @Resource
    private AuthenticationToolUtils authenticationToolUtils;

    @Resource
    private TokenService tokenService;

    @Resource
    private GraphqlArgumentsUtils graphqlArgumentsUtils;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        Class<?> clazz=AopUtils.getTargetClass(identificationMessenger);
//        if(HttpAuthorizationIdentificationMessenger.class.isAssignableFrom(clazz)){
//            ((HttpAuthorizationIdentificationMessenger)identificationMessenger).initIdentification();
//        }
        if (request.getDispatcherType() == DispatcherType.ASYNC) {
            //避免异步请求多次拦截带来的额外开销
            return true;
        }
        String method=request.getMethod();
        //排出掉一些非正常认证的拦截
        if(!List.of("POST","GET").contains(method)){
            return true;
        }
        /**
         * 打印请求头是否正确
         */
        request.getHeaderNames().asIterator().forEachRemaining(h->{
            logger.debug(h + ":" + request.getHeader(h));
        });
        if (handler instanceof HandlerMethod handlerMethod) {
            if(Objects.nonNull(AnnotationUtils.findAnnotation(handlerMethod.getMethod(), GraphqlApi.class))) {
                graphqlArgumentsUtils.init(request);
                GraphqlArguments arguments = graphqlArgumentsUtils.getArguments();
                String query = arguments.getQuery().replaceAll("\\s", " ");
                System.out.println(query);
                if (query.contains("IntrospectionQuery")) {
                    return true;
                }
            }
            Authentications typeAuthentications= AnnotationUtils.findAnnotation(handlerMethod.getMethod().getDeclaringClass(), Authentications.class);
            Authentications methodAuthentications = handlerMethod.getMethodAnnotation(Authentications.class);
            Stream<Authentication> authenticationStream=null;
            if(Objects.nonNull(typeAuthentications)&&Objects.nonNull(methodAuthentications)){
                authenticationStream=Stream.concat(Arrays.stream(typeAuthentications.value()), Arrays.stream(methodAuthentications.value()));
            }else if(Objects.nonNull(typeAuthentications)){
                authenticationStream= Arrays.stream(typeAuthentications.value());
            }else if(Objects.nonNull(methodAuthentications)){
                authenticationStream= Arrays.stream(methodAuthentications.value());
            }else{
                return true;
            }
            try {
                if (!this.tokenService.validToken()) {
//                    response.setStatus(HttpServletResponse.SC_OK);
//                    response.setCharacterEncoding("utf-8");
//                    response.setContentType(BasicController.APPLICATION_JSON_UTF8_VALUE);
//                    response.getWriter().write(JsonMap.createMap().append("code","ACCESS_TOKEN_INVALID").append("message","请求未认证")
//                            .jsonString()
//                    );
                    throw new ServiceException("ACCESS_TOKEN_INVALID:token错误");
                }
            }catch (Exception e){
                logger.error(e.getMessage(),e);
//                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
//                response.setCharacterEncoding("utf-8");
//                response.setContentType(BasicController.APPLICATION_JSON_UTF8_VALUE);
//                response.getWriter().write(JsonMap.createMap().append("code",HttpServletResponse.SC_UNAUTHORIZED).append("message","请求未认证")
//                        .jsonString()
//                );
                throw new ServiceException(e.getMessage(),e);
            }
            JsonMap<NamespaceAuthenticationConfig> configJsonMap=authenticationStream.reduce(JsonMap.<NamespaceAuthenticationConfig>createMap(),(m, a)->{
                NamespaceAuthenticationConfig config=m.get(a.namespace(),()->new NamespaceAuthenticationConfig(a.namespace(),new HashSet<>(),a.authorization(),a.tenant()));
                config.getAuthenticationActions().addAll(Arrays.stream(a.auth()).collect(Collectors.toSet()));
                config.setAuthorization(config.isAuthorization()||a.authorization());
                config.setTenant(config.isTenant()||a.tenant());
                return m;
            },(m,a)->null);
            String tenant= authenticationToolUtils.getTenant(request);
            String namespaces=authenticationToolUtils.getNamespace(request);
            NamespaceAuthenticationConfig config=configJsonMap.get("default");
//            boolean result=config.getAuthenticationActions().stream().reduce(true,(valid,action)->{
//                return switch (action){
//                    case USER ->
//                    default -> valid||true;
//                };
//            },(v,a)->null);
            if(configJsonMap.size()>0) {
                if(identificationMessenger.checkAuth()) {
                    identificationMessenger.initIdentification();
                }else{
                    return false;
                }
            }
            return true;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//        Class<?> clazz=AopUtils.getTargetClass(identificationMessenger);
//        if(HttpAuthorizationIdentificationMessenger.class.isAssignableFrom(clazz)){
//            ((HttpAuthorizationIdentificationMessenger)identificationMessenger).clear();
//        }
        identificationMessenger.clear();
    }


}

@Data
class NamespaceAuthenticationConfig{
    private String namespace;

    private Set<AuthenticationAction> authenticationActions;
    private boolean authorization;
    private boolean tenant;

    public NamespaceAuthenticationConfig(String namespace, Set<AuthenticationAction> authenticationActions, boolean authorization, boolean tenant) {
        this.namespace = namespace;
        this.authenticationActions = authenticationActions;
        this.authorization = authorization;
        this.tenant = tenant;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof NamespaceAuthenticationConfig that)) return false;
        return namespace.equals(that.namespace);
    }

    @Override
    public int hashCode() {
        return Objects.hash(namespace);
    }
}
